/**
 * @fileoverview Tests for message_set.js.
 */
goog.module('protobuf.runtime.MessageSetTest');

goog.setTestOnly();

const Kernel = goog.require('protobuf.runtime.Kernel');
const MessageSet = goog.require('protobuf.runtime.MessageSet');
const TestMessage = goog.require('protobuf.testing.binary.TestMessage');

/**
 * @param {...number} bytes
 * @return {!ArrayBuffer}
 */
function createArrayBuffer(...bytes) {
    return new Uint8Array(bytes).buffer;
}

describe('MessageSet does', () => {
    it('returns no messages for empty set', () => {
        const messageSet = MessageSet.createEmpty();
        expect(messageSet.getMessageOrNull(12345, TestMessage.instanceCreator))
            .toBeNull();
    });

    it('returns no kernel for empty set', () => {
        const messageSet = MessageSet.createEmpty();
        expect(messageSet.getMessageAccessorOrNull(12345)).toBeNull();
    });

    it('returns message that has been set', () => {
        const messageSet = MessageSet.createEmpty();
        const message = TestMessage.createEmpty();
        messageSet.setMessage(12345, message);
        expect(messageSet.getMessageOrNull(12345, TestMessage.instanceCreator))
            .toBe(message);
    });

    it('returns null for cleared message', () => {
        const messageSet = MessageSet.createEmpty();
        const message = TestMessage.createEmpty();
        messageSet.setMessage(12345, message);
        messageSet.clearMessage(12345);
        expect(messageSet.getMessageAccessorOrNull(12345)).toBeNull();
    });

    it('returns false for not present message', () => {
        const messageSet = MessageSet.createEmpty();
        expect(messageSet.hasMessage(12345)).toBe(false);
    });

    it('returns true for present message', () => {
        const messageSet = MessageSet.createEmpty();
        const message = TestMessage.createEmpty();
        messageSet.setMessage(12345, message);
        expect(messageSet.hasMessage(12345)).toBe(true);
    });

    it('returns false for cleared message', () => {
        const messageSet = MessageSet.createEmpty();
        const message = TestMessage.createEmpty();
        messageSet.setMessage(12345, message);
        messageSet.clearMessage(12345);
        expect(messageSet.hasMessage(12345)).toBe(false);
    });

    it('returns false for cleared message without it being present', () => {
        const messageSet = MessageSet.createEmpty();
        messageSet.clearMessage(12345);
        expect(messageSet.hasMessage(12345)).toBe(false);
    });

    const createMessageSet = () => {
        const messageSet = MessageSet.createEmpty();
        const message = TestMessage.createEmpty();
        message.setInt32(1, 2);
        messageSet.setMessage(12345, message);


        const parsedKernel =
            Kernel.fromArrayBuffer(messageSet.internalGetKernel().serialize());
        return MessageSet.fromKernel(parsedKernel);
    };

    it('pass through pivot for getMessageOrNull', () => {
        const messageSet = createMessageSet();
        const message =
            messageSet.getMessageOrNull(12345, TestMessage.instanceCreator, 2);
        expect(message.internalGetKernel().getPivot()).toBe(2);
    });

    it('pass through pivot for getMessageAttach', () => {
        const messageSet = createMessageSet();
        const message =
            messageSet.getMessageAttach(12345, TestMessage.instanceCreator, 2);
        expect(message.internalGetKernel().getPivot()).toBe(2);
    });

    it('pass through pivot for getMessageAccessorOrNull', () => {
        const messageSet = createMessageSet();
        const kernel = messageSet.getMessageAccessorOrNull(12345, 2);
        expect(kernel.getPivot()).toBe(2);
    });

    it('pick the last value in the stream', () => {
        const arrayBuffer = createArrayBuffer(
            0x52,  // Tag (field:10, length delimited)
            0x14,  // Length of 20 bytes
            0x0B,  // Start group fieldnumber 1
            0x10,  // Tag (field 2, varint)
            0xB9,  // 12345
            0x60,  // 12345
            0x1A,  // Tag (field 3, length delimited)
            0x03,  // length 3
            0xA0,  // Tag (fieldnumber 20, varint)
            0x01,  // Tag (fieldnumber 20, varint)
            0x1E,  // 30
            0x0C,  // Stop Group field number 1
            // second group
            0x0B,  // Start group fieldnumber 1
            0x10,  // Tag (field 2, varint)
            0xB9,  // 12345
            0x60,  // 12345
            0x1A,  // Tag (field 3, length delimited)
            0x03,  // length 3
            0xA0,  // Tag (fieldnumber 20, varint)
            0x01,  // Tag (fieldnumber 20, varint)
            0x01,  // 1
            0x0C   // Stop Group field number 1
        );

        const outerMessage = Kernel.fromArrayBuffer(arrayBuffer);

        const messageSet = outerMessage.getMessage(10, MessageSet.fromKernel);

        const message =
            messageSet.getMessageOrNull(12345, TestMessage.instanceCreator);
        expect(message.getInt32WithDefault(20)).toBe(1);
    });

    it('removes duplicates when read', () => {
        const arrayBuffer = createArrayBuffer(
            0x52,  // Tag (field:10, length delimited)
            0x14,  // Length of 20 bytes
            0x0B,  // Start group fieldnumber 1
            0x10,  // Tag (field 2, varint)
            0xB9,  // 12345
            0x60,  // 12345
            0x1A,  // Tag (field 3, length delimited)
            0x03,  // length 3
            0xA0,  // Tag (fieldnumber 20, varint)
            0x01,  // Tag (fieldnumber 20, varint)
            0x1E,  // 30
            0x0C,  // Stop Group field number 1
            // second group
            0x0B,  // Start group fieldnumber 1
            0x10,  // Tag (field 2, varint)
            0xB9,  // 12345
            0x60,  // 12345
            0x1A,  // Tag (field 3, length delimited)
            0x03,  // length 3
            0xA0,  // Tag (fieldnumber 20, varint)
            0x01,  // Tag (fieldnumber 20, varint)
            0x01,  // 1
            0x0C   // Stop Group field number 1
        );


        const outerMessage = Kernel.fromArrayBuffer(arrayBuffer);
        outerMessage.getMessageAttach(10, MessageSet.fromKernel);

        expect(outerMessage.serialize())
            .toEqual(createArrayBuffer(
                0x52,  // Tag (field:10, length delimited)
                0x0A,  // Length of 10 bytes
                0x0B,  // Start group fieldnumber 1
                0x10,  // Tag (field 2, varint)
                0xB9,  // 12345
                0x60,  // 12345
                0x1A,  // Tag (field 3, length delimited)
                0x03,  // length 3
                0xA0,  // Tag (fieldnumber 20, varint)
                0x01,  // Tag (fieldnumber 20, varint)
                0x01,  // 1
                0x0C   // Stop Group field number 1
            ));
    });

    it('allow for large typeIds', () => {
        const messageSet = MessageSet.createEmpty();
        const message = TestMessage.createEmpty();
        messageSet.setMessage(0xFFFFFFFE >>> 0, message);
        expect(messageSet.hasMessage(0xFFFFFFFE >>> 0)).toBe(true);
    });
});

describe('Optional MessageSet does', () => {
    // message Bar {
    //  optional MessageSet mset = 10;
    //}
    //
    // message Foo {
    //  extend proto2.bridge.MessageSet {
    //    optional Foo message_set_extension = 12345;
    //  }
    //  optional int32 f20 = 20;
    //}

    it('encode as a field', () => {
        const fooMessage = Kernel.createEmpty();
        fooMessage.setInt32(20, 30);

        const messageSet = MessageSet.createEmpty();
        messageSet.setMessage(12345, TestMessage.instanceCreator(fooMessage));

        const barMessage = Kernel.createEmpty();
        barMessage.setMessage(10, messageSet);

        expect(barMessage.serialize())
            .toEqual(createArrayBuffer(
                0x52,  // Tag (field:10, length delimited)
                0x0A,  // Length of 10 bytes
                0x0B,  // Start group fieldnumber 1
                0x10,  // Tag (field 2, varint)
                0xB9,  // 12345
                0x60,  // 12345
                0x1A,  // Tag (field 3, length delimited)
                0x03,  // length 3
                0xA0,  // Tag (fieldnumber 20, varint)
                0x01,  // Tag (fieldnumber 20, varint)
                0x1E,  // 30
                0x0C   // Stop Group field number 1
            ));
    });

    it('deserializes', () => {
        const fooMessage = Kernel.createEmpty();
        fooMessage.setInt32(20, 30);

        const messageSet = MessageSet.createEmpty();
        messageSet.setMessage(12345, TestMessage.instanceCreator(fooMessage));


        const barMessage = Kernel.createEmpty();
        barMessage.setMessage(10, messageSet);

        const arrayBuffer = barMessage.serialize();

        const barMessageParsed = Kernel.fromArrayBuffer(arrayBuffer);
        expect(barMessageParsed.hasFieldNumber(10)).toBe(true);

        const messageSetParsed =
            barMessageParsed.getMessage(10, MessageSet.fromKernel);

        const fooMessageParsed =
            messageSetParsed.getMessageOrNull(12345, TestMessage.instanceCreator)
                .internalGetKernel();

        expect(fooMessageParsed.getInt32WithDefault(20)).toBe(30);
    });
});
